package com.xbn.xbnmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xbn.common.to.es.SkuEsModel;
import com.xbn.common.utils.R;
import com.xbn.xbnmall.search.config.ElasticConfig;
import com.xbn.xbnmall.search.constant.EsConstant;
import com.xbn.xbnmall.search.feign.ProductFeignService;
import com.xbn.xbnmall.search.service.MallSearchService;
import com.xbn.xbnmall.search.vo.AttrResponseVo;
import com.xbn.xbnmall.search.vo.BrandVo;
import com.xbn.xbnmall.search.vo.SearchParam;
import com.xbn.xbnmall.search.vo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.swing.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author XBN
 * @date 2020/8/6 16:28
 * @email 78742541@qq.com
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Override
    public SearchResult search(SearchParam param) {
        SearchResult searchResult=null;
        SearchRequest searchRequest=bulidSearchRequest(param);
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticConfig.COMMON_OPITIONS);
       searchResult=bulidSearchResult(response,param);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return searchResult;
    }
    /*
    * 根据es的响应结果，构建出返回的查询结果
    * */
    private SearchResult bulidSearchResult(SearchResponse response,SearchParam param) {
        SearchResult searchResult = new SearchResult();

        //获取查询到的所有的商品
        SearchHits hits = response.getHits();
        List<SkuEsModel> esModels=new ArrayList<>();
        if(hits!=null&&hits.getHits().length>0) {
            for (SearchHit hit : hits.getHits()
            ) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel1 = JSON.parseObject(sourceAsString, SkuEsModel.class);
                    //如果是关键字检索就要设置高亮
               if(!StringUtils.isEmpty(param.getKeyword())) {
                   HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                   String highlightStr = skuTitle.getFragments()[0].string();
                   skuEsModel1.setSkuTitle(highlightStr);
               }
                esModels.add(skuEsModel1);
            }
        }
        searchResult.setProducts(esModels);

        //设置品牌
        List<SearchResult.BrandVo> brandVos=new ArrayList<>();
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        for ( Terms.Bucket bucket :brand_agg.getBuckets()
             ) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            //品牌的id
            long brandId = bucket.getKeyAsNumber().longValue();
                      //获取品牌的名字
            String brand_img = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg"))
                    .getBuckets().get(0).getKeyAsString();
            //获取品牌的名字
            String brand_name = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg"))
                    .getBuckets().get(0).getKeyAsString();
            brandVo.setBrandId(brandId);
            brandVo.setBrandImg(brand_img);
            brandVo.setBrandName(brand_name);
            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);


        //封装涉及到的所有属性的信息
        List<SearchResult.AttrVo> attrVos=new ArrayList<>();
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        //获取属性id聚合
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket: attr_id_agg.getBuckets()
             ) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            //属性名字
            String attr_name = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg"))
                    .getBuckets().get(0).getKeyAsString();
            //属性值
            List<String> attr_values = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg"))
                    .getBuckets().stream().map(item -> {
                        String keyAsString = ((Terms.Bucket) item).getKeyAsString();
                        return keyAsString;
                    }).collect(Collectors.toList());
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attr_name);
            attrVo.setAttrValue(attr_values);


            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);



        //拿到所有分类的聚合信息
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        List<SearchResult.CatalogVo> catalogVos=new ArrayList<>();
        for (Terms.Bucket bucket:buckets) {
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            String keyAsString = bucket.getKeyAsString();
            //获取id
            catalogVo.setCatalogId(Long.parseLong(keyAsString));
            //获取子聚合中的分类名
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalog_name_agg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }
       searchResult.setCatalogVos(catalogVos);



        //获取并设置总纪录数
        long total = hits.getTotalHits().value;
        searchResult.setTotal(total);
        //计算总页码
        int totalPages= (int) (total%EsConstant.PRODUCT_PAGESIZE==0?
                        total/EsConstant.PRODUCT_PAGESIZE:(total/EsConstant.PRODUCT_PAGESIZE+1));
        searchResult.setTotalPages(totalPages);
        //当前页码
        searchResult.setPageNum(param.getPageNum());

        List<Integer> pageNavs=new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }


            //属性的面包屑导航
        if(param.getAttrs()!=null&&param.getAttrs().size()>0) {
            //可访问的页码数据
            searchResult.setPageNavs(pageNavs);
            List<SearchResult.NavVo> navVos = param.getAttrs().stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);
                R r = productFeignService.getAttrInfo(Long.parseLong(s[0]));
                //设置哪些属性id已经被选择了，方便前端页面展示
                searchResult.getAttrIds().add(Long.valueOf(s[0]));
                if (r.getCode() == 0) {
                    AttrResponseVo attrResponseVo = r.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    navVo.setNavName(attrResponseVo.getAttrName());
                } else {
                    navVo.setNavName("error");
                }

                //访问地址处理
                String replace = replaceQueryString(param, attr,"attrs");
                navVo.setLink("http://search.xbnmall.com/list.html?"+replace);
                return navVo;
            }).collect(Collectors.toList());

            searchResult.setNavs(navVos);
        }
        //品牌的面包屑导航
        if(param.getBrandId()!=null&&param.getBrandId().size()>0)
        {
            List<SearchResult.NavVo> navs = searchResult.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();

            navVo.setNavName("品牌");
            R r = productFeignService.brandInfos(param.getBrandId());
            if(r.getCode()==0)
            {
                List<BrandVo> brands = r.getData("brand", new TypeReference<List<BrandVo>>() {
                });
                StringBuffer buffer=new StringBuffer();
                String replace="";
                for (BrandVo b :brands) {
                    buffer.append(b.getBrandName()+";");
                     replace= replaceQueryString(param, b.getBrandId()+"","brandId");
                }
                navVo.setNavValue(buffer.toString());
                navVo.setLink(replace);
            }

            navs.add(navVo);
        }
        //TODO 分类的面包屑菜单

        return searchResult;
    }

    private String replaceQueryString(SearchParam param, String val,String key) {
        String encode=null;
        try {
            encode= URLEncoder.encode(val,"UTF-8");
            //空格在浏览器会被编码为%20，而在java Encoder中是+
            encode.replace("+","%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return param.get_queryString().replace("&"+key+"=" + encode, "")
                .replace("?attrs=" + encode, "");
    }

    /*
    * 根据条件构建检索请求
    * */
    private SearchRequest bulidSearchRequest(SearchParam param) {
        //所有请求条件的构建都要使用sourceBulid来构建
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //构建出一个bool bulid
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //构建must条件
        if(!StringUtils.isEmpty(param.getKeyword()))
        {
            boolQueryBuilder.must(
                    QueryBuilders.matchQuery("skuTitle",param.getKeyword())
            );
        }
        //构建filter条件，filter查询条件不参与评分
        //按照三级分类id查询
        if(param.getCatalog3Id()!=null)
        {
        boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId"
        ,param.getCatalog3Id()));
        }
        //按照品牌的id进行查询
        if(param.getBrandId()!=null&&param.getBrandId().size()>0)
        {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",
                    param.getBrandId()));
        }
        //按照库存是否有库存进行查询
        if(param.getHasStock()!=null) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery(
                    "hasStock", param.getHasStock() == 1
            ));
        }
        //按照价格区间进行检索
        if(!StringUtils.isEmpty(param.getSkuPrice()))
        {
            //1_500   _500      500_
            RangeQueryBuilder rangePrice = QueryBuilders.rangeQuery("skuPrice");
            String[] s=param.getSkuPrice().split("_");
            if(s.length==2)
            {
                rangePrice.gte(s[0]).lte(s[1]);
            }
            else if(s.length==1)
            {
                if(param.getSkuPrice().startsWith("_"))
                {
                    //小于
                    rangePrice.lt(s[0]);
                }

                if(param.getSkuPrice().endsWith("_"))
                {
                    //大于
                    rangePrice.gte(s[0]);
                }
            }

            boolQueryBuilder.filter(rangePrice);
        }
        //按照属性进行查询
        if(param.getAttrs()!=null&&param.getAttrs().size()>0)
        {

            //attrs=1_5寸:8寸
            for (String attr :
                    param.getAttrs()) {
                BoolQueryBuilder nestedBoolQueryBuilder = QueryBuilders.boolQuery();
                String[] s=attr.split("_");
                //检索属性id
                String attrId=s[0];
                //检索属性值
                String[] attrValues = s[1].split(":");
                nestedBoolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrId",attrId));
                nestedBoolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                //属性是嵌入式的,每一个条件都要生成一个嵌入式的查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQueryBuilder, ScoreMode.None);
                boolQueryBuilder.filter(nestedQuery);
            }
        }

        //排序  sort=hotScore_desc
        if(!StringUtils.isEmpty(param.getSort()))
        {
            String sort=param.getSort();
            String[] s = sort.split("_");
            org.elasticsearch.search.sort.SortOrder order=s[1].equalsIgnoreCase("asc")? SortOrder.ASC:
                    SortOrder.DESC;
            sourceBuilder.sort(s[0],order);
        }

        //分页
        sourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        //高亮，只有模糊匹配的关键字才需要高亮
        if(!StringUtils.isEmpty(param.getKeyword()))
        {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }

        //聚合分析
        //1品牌聚合，找出查询结果的商品中涉及到的所有的品牌
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg")
               .field("brandId") ;
        brand_agg.size(50);//商品聚合50个
        sourceBuilder.aggregation(brand_agg);

        //品牌聚合的子聚合
        //每个品牌id对应的品牌名字或者图片肯定只有一个，所以这里size  1就ok了
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
        .field("brandName").size(1));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
        .field("brandImg").size(1));


        //分类聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId")
                .size(20);
        //分类子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg")
        .field("catalogName").size(1));
        sourceBuilder.aggregation(catalog_agg);

        //属性聚合,属性聚合是嵌入式的聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
       //属性子聚合
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attr_agg.subAggregation(attr_id_agg);
        //子聚合中还有子聚合，attr_id_age下面还有一个子聚合,分析出attrId对应的attrName
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg")
                .field("attrs.attrName").size(1));
        //聚合出attrId对应的所有属性的值
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg")
        .field("attrs.attrValue").size(50));
        sourceBuilder.aggregation(attr_agg);




        sourceBuilder.query(boolQueryBuilder);
        String s = sourceBuilder.toString();
        System.out.println("构建的查询语句:"+s);
        SearchRequest searchRequest = new SearchRequest(
                new String[]{EsConstant.PRODUCT_INDEX},
                sourceBuilder
        );

        //attrValue要根据";"进行多选值筛选
        return searchRequest;
    }
}
